<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<link rel="import" href="dom-innerHTML.html">
<script>
(function() {

  'use strict';

  // native add/remove
  var nativeInsertBefore = Element.prototype.insertBefore;
  var nativeAppendChild = Element.prototype.appendChild;
  var nativeRemoveChild = Element.prototype.removeChild;

  /**
   * TreeApi is a dom manipulation library used by Shady/Polymer.dom to
   * manipulate composed and logical trees.
   */
  Polymer.TreeApi = {

    // sad but faster than slice...
    arrayCopyChildNodes: function(parent) {
      var copy=[], i=0;
      for (var n=parent.firstChild; n; n=n.nextSibling) {
        copy[i++] = n;
      }
      return copy;
    },

    arrayCopyChildren: function(parent) {
      var copy=[], i=0;
      for (var n=parent.firstElementChild; n; n=n.nextElementSibling) {
        copy[i++] = n;
      }
      return copy;
    },

    arrayCopy: function(a$) {
      var l = a$.length;
      var copy = new Array(l);
      for (var i=0; i < l; i++) {
        copy[i] = a$[i];
      }
      return copy;
    }

  };

  Polymer.TreeApi.Logical = {

    hasParentNode: function(node) {
      return Boolean(node.__dom && node.__dom.parentNode);
    },

    hasChildNodes: function(node) {
      return Boolean(node.__dom && node.__dom.childNodes !== undefined);
    },

    getChildNodes: function(node) {
      // note: we're distinguishing here between undefined and false-y:
      // hasChildNodes uses undefined check to see if this element has logical
      // children; the false-y check indicates whether or not we should rebuild
      // the cached childNodes array.
      return this.hasChildNodes(node) ? this._getChildNodes(node) :
        node.childNodes;
    },

    _getChildNodes: function(node) {
      if (!node.__dom.childNodes) {
        node.__dom.childNodes = [];
        for (var n=node.__dom.firstChild; n; n=n.__dom.nextSibling) {
          node.__dom.childNodes.push(n);
        }
      }
      return node.__dom.childNodes;
    },

    // NOTE: __dom can be created under 2 conditions: (1) an element has a
    // logical tree, or (2) an element is in a logical tree. In case (1), the
    // element will store firstChild/lastChild, and in case (2), the element
    // will store parentNode, nextSibling, previousSibling. This means that
    // the mere existence of __dom is not enough to know if the requested
    // logical data is available and instead we do an explicit undefined check.
    getParentNode: function(node) {
      return node.__dom && node.__dom.parentNode !== undefined ?
        node.__dom.parentNode : node.parentNode;
    },

    getFirstChild: function(node) {
      return node.__dom && node.__dom.firstChild !== undefined ?
        node.__dom.firstChild : node.firstChild;
    },

    getLastChild: function(node) {
      return node.__dom && node.__dom.lastChild  !== undefined ?
        node.__dom.lastChild : node.lastChild;
    },

    getNextSibling: function(node) {
      return node.__dom && node.__dom.nextSibling  !== undefined ?
        node.__dom.nextSibling : node.nextSibling;
    },

    getPreviousSibling: function(node) {
      return node.__dom && node.__dom.previousSibling  !== undefined ?
        node.__dom.previousSibling : node.previousSibling;
    },

    getFirstElementChild: function(node) {
      return node.__dom && node.__dom.firstChild !== undefined ?
        this._getFirstElementChild(node) : node.firstElementChild;
    },

    _getFirstElementChild: function(node) {
      var n = node.__dom.firstChild;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.nextSibling;
      }
      return n;
    },

    getLastElementChild: function(node) {
      return node.__dom && node.__dom.lastChild !== undefined ?
        this._getLastElementChild(node) : node.lastElementChild;
    },

    _getLastElementChild: function(node) {
      var n = node.__dom.lastChild;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.previousSibling;
      }
      return n;
    },

    getNextElementSibling: function(node) {
      return node.__dom && node.__dom.nextSibling !== undefined ?
        this._getNextElementSibling(node) : node.nextElementSibling;
    },

    _getNextElementSibling: function(node) {
      var n = node.__dom.nextSibling;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.nextSibling;
      }
      return n;
    },

    getPreviousElementSibling: function(node) {
      return node.__dom && node.__dom.previousSibling !== undefined ?
        this._getPreviousElementSibling(node) : node.previousElementSibling;
    },

    _getPreviousElementSibling: function(node) {
      var n = node.__dom.previousSibling;
      while (n && n.nodeType !== Node.ELEMENT_NODE) {
        n = n.__dom.previousSibling;
      }
      return n;
    },

    // Capture the list of light children. It's important to do this before we
    // start transforming the DOM into "rendered" state.
    // Children may be added to this list dynamically. It will be treated as the
    // source of truth for the light children of the element. This element's
    // actual children will be treated as the rendered state once this function
    // has been called.
    saveChildNodes: function(node) {
      if (!this.hasChildNodes(node)) {
        node.__dom = node.__dom || {};
        node.__dom.firstChild = node.firstChild;
        node.__dom.lastChild = node.lastChild;
        node.__dom.childNodes = [];
        for (var n=node.firstChild; n; n=n.nextSibling) {
          n.__dom = n.__dom || {};
          n.__dom.parentNode = node;
          node.__dom.childNodes.push(n);
          n.__dom.nextSibling = n.nextSibling;
          n.__dom.previousSibling = n.previousSibling;
        }
      }
    },

    recordInsertBefore: function(node, container, ref_node) {
      container.__dom.childNodes = null;
      // handle document fragments
      if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
        // TODO(sorvell): remember this for patching:
        // the act of setting this info can affect patched nodes
        // getters; therefore capture childNodes before patching.
        for (var n=node.firstChild; n; n=n.nextSibling) {
          this._linkNode(n, container, ref_node);
        }
      } else {
        this._linkNode(node, container, ref_node);
      }
    },

    _linkNode: function(node, container, ref_node) {
      node.__dom = node.__dom || {};
      container.__dom = container.__dom || {};
      if (ref_node) {
        ref_node.__dom = ref_node.__dom || {};
      }
      // update ref_node.previousSibling <-> node
      node.__dom.previousSibling = ref_node ? ref_node.__dom.previousSibling :
        container.__dom.lastChild;
      if (node.__dom.previousSibling) {
        node.__dom.previousSibling.__dom.nextSibling = node;
      }
      // update node <-> ref_node
      node.__dom.nextSibling = ref_node || null;
      if (node.__dom.nextSibling) {
        node.__dom.nextSibling.__dom.previousSibling = node;
      }
      // update node <-> container
      node.__dom.parentNode = container;
      if (ref_node) {
        if (ref_node === container.__dom.firstChild) {
          container.__dom.firstChild = node;
        }
      } else {
        container.__dom.lastChild = node;
        if (!container.__dom.firstChild) {
          container.__dom.firstChild = node;
        }
      }
      // remove caching of childNodes
      container.__dom.childNodes = null;
    },

    recordRemoveChild: function(node, container) {
      node.__dom = node.__dom || {};
      container.__dom = container.__dom || {};
      if (node === container.__dom.firstChild) {
        container.__dom.firstChild = node.__dom.nextSibling;
      }
      if (node === container.__dom.lastChild) {
        container.__dom.lastChild = node.__dom.previousSibling;
      }
      var p = node.__dom.previousSibling;
      var n = node.__dom.nextSibling;
      if (p) {
        p.__dom.nextSibling = n;
      }
      if (n) {
        n.__dom.previousSibling = p;
      }
      // When an element is removed, logical data is no longer tracked.
      // Explicitly set `undefined` here to indicate this. This is disginguished
      // from `null` which is set if info is null.
      node.__dom.parentNode = node.__dom.previousSibling =
        node.__dom.nextSibling = undefined;
      // remove caching of childNodes
      container.__dom.childNodes = null;
    }

  }

  // TODO(sorvell): composed tree manipulation is made available
  // (1) to maninpulate the composed tree, and (2) to track changes
  // to the tree for optional patching pluggability.
  Polymer.TreeApi.Composed = {

    getChildNodes: function(node) {
      return Polymer.TreeApi.arrayCopyChildNodes(node);
    },

    getParentNode: function(node) {
      return node.parentNode;
    },

    // composed tracking needs to reset composed children here in case
    // they may have already been set (this shouldn't happen but can
    // if dependency ordering is incorrect and as a result upgrade order
    // is unexpected)
    clearChildNodes: function(node) {
      node.textContent = '';
    },

    insertBefore: function(parentNode, newChild, refChild) {
      return nativeInsertBefore.call(parentNode, newChild, refChild || null);
    },

    appendChild: function(parentNode, newChild) {
      return nativeAppendChild.call(parentNode, newChild);
    },

    removeChild: function(parentNode, node) {
      return nativeRemoveChild.call(parentNode, node);
    }

  };

})();
</script>
